const fs = require('fs');
const path = require('path');
const axios = require('axios');
const debug = require('debug')('wechat');
const crypto = require('crypto');

const conf = {
  URL: 'https://api.weixin.qq.com',
  APP_ID: 'wxbcfd7bfe6b773736',
  APP_SECRET: '698a66e32cd241aca94ca284ee2f5b94'
}

conf.TOKEN_FILE = path.resolve(__dirname, './cache/app_token.json');
conf.TICKET_FILE = path.resolve(__dirname, './cache/app_ticket.json');

axios.defaults.headers.post['Content-Type'] = 'application/json';
axios.defaults.baseURL = conf.URL;
// axios.interceptors.response.use(res => {
//   return res;
// });

const cache = new Map();
cache.set('app_token', { access_token: null, expires: 0 });
cache.set('app_ticket', { ticket: null, expires: 0 });

// 锁定并发请求，等待同一个promise结果
let ticketPromise = null;
let tokenPromise = null;

function getTimesTamp() {
  return parseInt(Date.now() / 1000) + '';
}

function getNonceStr() {
  return Math.random().toString(36).substr(2, 15);
}

/**
 * getAccessToken 获取 access_token
 */
async function getAccessToken() {

  // 使用全局变量中的 access_token
  const cacheToken = cache.get('app_token');
  if (cacheToken.access_token && Date.now() < cacheToken.expires) {
    debug('使用全局变量中的[access_token]');
    return cacheToken.access_token;
  }

  // 使用缓存文件中的 access_token
  const fileToken = fs.existsSync(conf.TOKEN_FILE) && JSON.parse(fs.readFileSync(conf.TOKEN_FILE));
  if (fileToken && fileToken.access_token && Date.now() < fileToken.expires) {
    cache.set('app_token', {
      access_token: fileToken.access_token,
      expires: fileToken.expires
    });
    debug('使用缓存文件中的[access_token]');
    return fileToken.access_token;
  }

  // 从微信服务器获取 access_token
  if (!tokenPromise) {
    debug('从微信服务器获取[access_token]');
    tokenPromise = axios.get(`/cgi-bin/token`, {
      params: {
        grant_type: 'client_credential',
        appId: conf.APP_ID,
        secret: conf.APP_SECRET
      }
    })
  } else {
    debug('[access_token]请求重复，等待返回结果');
  }

  const res = await tokenPromise;
  tokenPromise = null;

  const { access_token, expires_in } = res.data;
  // 设置为到期前5分钟重新刷新
  const expires = Date.now() + (expires_in * 1000 - 300000);
  cache.set('app_token', { access_token, expires });
  // 写入
  fs.writeFileSync(conf.TOKEN_FILE, JSON.stringify({ access_token, expires }));
  return access_token;
}

/**
 * 强制刷新token
 */
function refreshToken() {
  return axios.get(`/cgi-bin/token`, {
    params: {
      grant_type: 'client_credential',
      appId: conf.APP_ID,
      secret: conf.APP_SECRET
    }
  })
}

/**
 * getTicket 获取 ticket
 */
async function getTicket() {
  // 使用全局变量中的 ticket
  const cacheTicket = cache.get('app_ticket');
  if (cacheTicket.ticket && Date.now() < cacheTicket.expires) {
    debug('使用全局变量中的[ticket]');
    return cacheTicket.ticket;
  }
  // 使用缓存文件中的 ticket
  const fileTicket = fs.existsSync(conf.TICKET_FILE) && JSON.parse(fs.readFileSync(conf.TICKET_FILE));
  if (fileTicket && fileTicket.ticket && Date.now() < fileTicket.expires) {
    cache.set('app_ticket', {
      ticket: fileTicket.ticket,
      expires: fileTicket.expires
    });
    debug('使用缓存文件中的[ticket]');
    return fileTicket.ticket;
  }

  // 处理并发
  if (!ticketPromise) {
    debug('从微信服务器获取[ticket]');
    // 从微信服务器获取ticket
    ticketPromise = getAccessToken().then(access_token => {
      return axios.get(`/cgi-bin/ticket/getticket`, {
        params: {
          type: 'jsapi',
          access_token,
        }
      });
    })
  } else {
    debug('[ticket]请求重复，等待返回结果');
  }

  const res = await ticketPromise;
  ticketPromise = null

  const { ticket, expires_in } = res.data;

  // 设置为到期前5分钟重新刷新
  const expires = Date.now() + (expires_in * 1000 - 300000);
  cache.set('app_ticket', { ticket, expires });

  fs.writeFileSync(conf.TICKET_FILE, JSON.stringify({ ticket, expires }));
  return ticket;
}

/**
 * getSignature 生成jssdk调用签名
 * @param {object} obj 入参
 * @property {string} obj.url 需要签名的URL
 * @see: https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
 */
async function getSignature({ url }) {
  const ticket = await getTicket();
  const timestamp = getTimesTamp();
  const noncestr = getNonceStr();
  const str = `jsapi_ticket=${ticket}&noncestr=${noncestr}&timestamp=${timestamp}&url=${url}`;
  var signature = crypto.createHash('sha1').update(str).digest('hex');
  return {
    appId: conf.APP_ID,
    timestamp,
    noncestr,
    signature
  }
}

// 读取自定义菜单信息
async function getMenu() {
  const access_token = await getAccessToken();
  const res = await axios.get(`/cgi-bin/get_current_selfmenu_info`, {
    params: { access_token }
  });
  return res.data;
}

// 设置自定义菜单信息
async function setMenu(json) {
  const access_token = await getAccessToken();
  const res = await axios.post(`/cgi-bin/menu/create?access_token=${access_token}`, json);
  return res.data;
}

/**
 * getUserInfo 获取用基本信息
 * @param {string} openid 用户OPENID
 */
async function getUserInfo(openid) {
  const access_token = await getAccessToken();
  const res = await axios.get(`/cgi-bin/user/info`, {
    params: { access_token, openid }
  });
  return res.data;
}

module.exports = {
  getAccessToken,
  getTicket,
  getSignature,
  getMenu,
  setMenu,
  getUserInfo
}